Skip to content

Conversation

olemartinorg
Copy link
Contributor

@olemartinorg olemartinorg commented Sep 24, 2025

Description

Refactoring to fix issues with component lookups inside repeating groups by using DataModelLocationProvider properly. This used to look up expression data sources for everything at once, thus missing the nuances of different rows in repeating groups.

Related Issue(s)

Verification/QA

  • Manual functionality testing
    • I have tested these changes manually
    • Creator of the original issue (or service owner) has been contacted for manual testing (or will be contacted when released in alpha)
    • No testing done/necessary
  • Automated tests
    • Unit test(s) have been added/updated
    • Cypress E2E test(s) have been added/updated
    • No automatic tests are needed here (no functional changes/additions)
    • I want someone to help me make some tests
  • UU/WCAG (follow these guidelines until we have our own)
    • I have tested with a screen reader/keyboard navigation/automated wcag validator
    • No testing done/necessary (no DOM/visual changes)
    • I want someone to help me perform accessibility testing
  • User documentation @ altinn-studio-docs
    • Has been added/updated
    • No functionality has been changed/added, so no documentation is needed
    • I will do that later/have created an issue
  • Support in Altinn Studio
    • Issue(s) created for support in Studio
    • This change/feature does not require any changes to Altinn Studio
  • Sprint board
    • The original issue (or this PR itself) has been added to the Team Apps project and to the current sprint board
    • I don't have permissions to do that, please help me out
  • Labels
    • I have added a kind/* and backport* label to this PR for proper release notes grouping
    • I don't have permissions to add labels, please help me out

Summary by CodeRabbit

  • New Features

    • Validation now correctly aggregates and evaluates per-field rules across nested and repeating groups, improving accuracy for component-based checks.
  • Bug Fixes

    • Hidden fields no longer cause validations or incorrect error placements in repeating/nested structures.
    • Missing group contexts are detected and reported to avoid inconsistent validation states.
  • Tests

    • Added end-to-end scenarios covering component lookups, hidden-state handling, and error visibility in repeating groups.

Copy link
Contributor

coderabbitai bot commented Sep 24, 2025

📝 Walkthrough

Walkthrough

Adds NestedDataModelLocationProviders and helpers to derive repeating-group contexts; refactors expression validation to per-field collection with a validation collector; introduces FD.useDebouncedAllPaths to enumerate concrete field paths; adjusts a binding-validation capture; and adds tests/fixtures for component lookups and hidden-state handling in repeating groups.

Changes

Cohort / File(s) Change summary
Data model location utilities
src/utils/layout/DataModelLocation.tsx
Adds parseGroupContexts and the exported NestedDataModelLocationProviders; introduces LocationProps and NestedLocationProps; tightens DataModelLocationProvider prop typing and implements nested provider stacking for indexed paths.
Expression evaluation & runner tests
src/features/expressions/shared-functions.test.tsx
Replaces prior inline DataModelLocationFor wrapping with NestedDataModelLocationProviders; builds fieldSegments from parentIds/rowIndices, computes segment names, enforces presence of group binding, adjusts traversal order (outermost first), and forwards evaluated expressions via InnerExpressionRunner.
Expression validation refactor
src/features/validation/expressionValidation/ExpressionValidation.tsx
Reworks validation to use a per-field collector API and new internal components (DataTypeValidation, FieldExpressionValidation, etc.); aggregates FieldValidations via state/effect; adopts NestedDataModelLocationProviders and per-field dataSource composition; preserves evalExpr usage.
Form data APIs
src/features/formData/FormDataWrite.tsx
Adds collectMatchingFieldPaths helper and exports FD.useDebouncedAllPaths(reference) which prefers layout lookup results and otherwise enumerates concrete matching paths from debounced data; supports undefined references.
Save-to-group validation minor fix
src/features/saveToGroup/layoutValidation.ts
Changes capture of validateDataModelBindingsSimple return from array-destructuring to single-value assignment (const newErrors = ...) while preserving existing aggregation behavior.
Tests and fixtures
src/features/validation/expressionValidation/shared-expression-validation-tests/component-lookup-hidden.json, test/e2e/integration/expression-validation-test/expression-validation.ts
Adds JSON fixture for component lookup with hidden-state in repeating groups; adds e2e scenarios asserting component-based validation and visibility-dependent errors; introduces type-only imports for test shaping.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The description follows the repository template by including sections for Description, Related Issue(s), and Verification/QA, but the Related Issue(s) section does not reference a GitHub issue with a proper “closes #” entry and instead uses a Slack link, leaving the issue linkage incomplete. Please update the Related Issue(s) section to reference a GitHub issue number with a “closes #” entry or explicitly state that no issue is being closed to align with the repository’s template requirements.
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (1 passed)
Check name Status Explanation
Title Check ✅ Passed The title concisely identifies the primary change by naming the refactored module, ExpressionValidation, and clearly signals to reviewers that the pull request focuses on refactoring that component without extraneous detail.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bug/component-lookups-expression-validation

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@olemartinorg olemartinorg added kind/bug Something isn't working backport This PR should be cherry-picked onto older release branches labels Sep 24, 2025
@olemartinorg
Copy link
Contributor Author

/publish

Copy link
Contributor

github-actions bot commented Sep 24, 2025

PR release:

  • <link rel="stylesheet" type="text/css" href="https://altinncdn.no/toolkits/altinn-app-frontend/4.22.0-pr.2454.component-lookups-expression-validation.de7e932c/altinn-app-frontend.css">
  • <script src="https://altinncdn.no/toolkits/altinn-app-frontend/4.22.0-pr.2454.component-lookups-expression-validation.de7e932c/altinn-app-frontend.js"></script>

⚙️ Building...
✅ Done!

@olemartinorg olemartinorg force-pushed the bug/component-lookups-expression-validation branch from de7e932 to d77219c Compare September 30, 2025 11:08
@olemartinorg olemartinorg changed the base branch from main to bug/tag-validation September 30, 2025 11:08
@olemartinorg olemartinorg changed the title WIP: Refactoring ExpressionValidation Refactoring ExpressionValidation Sep 30, 2025
@olemartinorg olemartinorg added backport-ignore This PR is a new feature and should not be cherry-picked onto release branches and removed backport This PR should be cherry-picked onto older release branches labels Sep 30, 2025
@olemartinorg olemartinorg marked this pull request as ready for review September 30, 2025 14:17
Copy link
Contributor

@paal2707 paal2707 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ser bra ut!

Base automatically changed from bug/tag-validation to main October 3, 2025 07:28
Ole Martin Handeland added 8 commits October 3, 2025 09:31
…kily this made a test fail, but I went down the wrong track when debugging it. The function was always supposed to return the input if nothing else was found. Also, the current value of the field is not really needed as an input for useEffect()
… do not want to run expression validation for a row when there are no rows, so the last solution didn't work as we wanted (it caused lots of errors in the developer tools log)
@olemartinorg olemartinorg force-pushed the bug/component-lookups-expression-validation branch from 2f896e7 to 7a64e36 Compare October 3, 2025 07:31
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 831824d and 7a64e36.

📒 Files selected for processing (7)
  • src/features/expressions/shared-functions.test.tsx (3 hunks)
  • src/features/formData/FormDataWrite.tsx (2 hunks)
  • src/features/saveToGroup/layoutValidation.ts (1 hunks)
  • src/features/validation/expressionValidation/ExpressionValidation.tsx (2 hunks)
  • src/features/validation/expressionValidation/shared-expression-validation-tests/component-lookup-hidden.json (1 hunks)
  • src/utils/layout/DataModelLocation.tsx (2 hunks)
  • test/e2e/integration/expression-validation-test/expression-validation.ts (2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Avoid using any and unnecessary type casts (as Type) in TypeScript; prefer precise typings and refactor existing casts/anys
For TanStack Query, use objects to manage query keys and functions, and centralize shared options via queryOptions

Files:

  • src/features/formData/FormDataWrite.tsx
  • src/utils/layout/DataModelLocation.tsx
  • src/features/saveToGroup/layoutValidation.ts
  • test/e2e/integration/expression-validation-test/expression-validation.ts
  • src/features/validation/expressionValidation/ExpressionValidation.tsx
  • src/features/expressions/shared-functions.test.tsx
**/*.test.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

In tests, use renderWithProviders from src/test/renderWithProviders.tsx to supply required form layout context

Files:

  • src/features/expressions/shared-functions.test.tsx
🧠 Learnings (1)
📚 Learning: 2025-08-22T13:53:28.252Z
Learnt from: CR
PR: Altinn/app-frontend-react#0
File: CLAUDE.md:0-0
Timestamp: 2025-08-22T13:53:28.252Z
Learning: Applies to **/*.test.{ts,tsx} : In tests, use `renderWithProviders` from `src/test/renderWithProviders.tsx` to supply required form layout context

Applied to files:

  • src/features/expressions/shared-functions.test.tsx
🧬 Code graph analysis (5)
src/features/formData/FormDataWrite.tsx (1)
src/features/datamodel/DataModelsProvider.tsx (1)
  • DataModels (380-421)
src/features/saveToGroup/layoutValidation.ts (1)
src/utils/layout/generator/validation/hooks.ts (1)
  • validateDataModelBindingsSimple (58-77)
test/e2e/integration/expression-validation-test/expression-validation.ts (2)
src/features/validation/index.ts (1)
  • IExpressionValidationConfig (263-266)
src/features/expressions/types.ts (1)
  • Expression (71-71)
src/features/validation/expressionValidation/ExpressionValidation.tsx (5)
src/features/validation/index.ts (2)
  • FieldValidations (92-94)
  • IExpressionValidation (222-227)
src/features/datamodel/DataModelsProvider.tsx (1)
  • DataModels (380-421)
src/utils/layout/DataModelLocation.tsx (1)
  • NestedDataModelLocationProviders (170-188)
src/utils/layout/useExpressionDataSources.ts (2)
  • useExpressionDataSources (104-167)
  • ExpressionDataSources (37-55)
src/features/expressions/index.ts (1)
  • evalExpr (70-118)
src/features/expressions/shared-functions.test.tsx (5)
src/features/form/layout/LayoutsContext.tsx (1)
  • useLayoutLookups (90-90)
src/features/expressions/expression-functions.ts (1)
  • component (427-463)
src/layout/layout.ts (1)
  • IDataModelBindings (61-64)
src/features/form/layout/utils/repeating.ts (2)
  • RepeatingComponents (5-5)
  • getRepeatingBinding (21-37)
src/utils/layout/DataModelLocation.tsx (1)
  • NestedDataModelLocationProviders (170-188)
🪛 Biome (2.1.2)
src/utils/layout/DataModelLocation.tsx

[error] 179-182: Missing key property for this element in iterable.

The order of the items may change, and having a key can help React identify which item was moved.
Check the React documentation.

(lint/correctness/useJsxKeyInIterable)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Install
  • GitHub Check: Analyze (javascript)
  • GitHub Check: Type-checks, eslint, unit tests and SonarCloud
🔇 Additional comments (1)
src/features/saveToGroup/layoutValidation.ts (1)

56-56: LGTM! Correctness fix for return type handling.

The change correctly removes the array destructuring since validateDataModelBindingsSimple returns string[] directly, not a tuple. The previous destructuring would have incorrectly extracted the first string element instead of the array itself.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
src/features/formData/FormDataWrite.tsx (1)

675-702: Fix TypeScript indexing of unknown data in path collector.

Lines 688 and 692 perform element access on data which is typed as unknown. Even with the type guard on line 688, TypeScript still treats data as unknown afterward. Under strict TypeScript configurations (such as noUncheckedIndexedAccess or similar), indexing unknown is a compile error.

Apply this diff to add a safe cast after the type guard:

 function collectMatchingFieldPaths(
   data: unknown,
   fieldParts: string[],
   currentPath: string,
   partIndex: number,
   results: string[],
 ) {
   if (partIndex >= fieldParts.length) {
     results.push(currentPath);
     return;
   }
 
   const part = fieldParts[partIndex];
-  if (typeof data !== 'object' || data === null || data[part] === undefined || data[part] === null) {
+  if (typeof data !== 'object' || data === null) {
     return;
   }
 
-  const nextData = data[part];
+  const container = data as Record<string, unknown>;
+  const nextData = container[part];
+  if (nextData === undefined || nextData === null) {
+    return;
+  }
+
   const nextPath = currentPath ? `${currentPath}.${part}` : part;
 
   if (Array.isArray(nextData)) {
     for (let i = 0; i < nextData.length; i++) {
       collectMatchingFieldPaths(nextData[i], fieldParts, `${nextPath}[${i}]`, partIndex + 1, results);
     }
   } else {
     collectMatchingFieldPaths(nextData, fieldParts, nextPath, partIndex + 1, results);
   }
 }
src/utils/layout/DataModelLocation.tsx (1)

177-188: Fix Biome lint error by replacing reduceRight with a reverse loop.

Biome flags groupContexts.reduceRight for lint/correctness/useJsxKeyInIterable because JSX is returned from an iterable callback. Even though a key is provided, Biome requires a different pattern to satisfy this rule.

Apply this diff to replace the reduceRight with a reverse for-loop:

-  // Recursively nest the providers from outermost to innermost
-  return groupContexts.reduceRight(
-    (child, { groupBinding, rowIndex }) => (
-      <DataModelLocationProvider
-        key={`${groupBinding.dataType}-${groupBinding.field}-${rowIndex}`}
-        groupBinding={groupBinding}
-        rowIndex={rowIndex}
-      >
-        {child}
-      </DataModelLocationProvider>
-    ),
-    children as React.ReactElement,
-  );
+  // Nest the providers from outermost to innermost
+  let wrappedChildren: React.ReactNode = children;
+  for (let i = groupContexts.length - 1; i >= 0; i--) {
+    const { groupBinding, rowIndex } = groupContexts[i];
+    wrappedChildren = (
+      <DataModelLocationProvider
+        key={`${groupBinding.field}-${rowIndex}`}
+        groupBinding={groupBinding}
+        rowIndex={rowIndex}
+      >
+        {wrappedChildren}
+      </DataModelLocationProvider>
+    );
+  }
+  return <>{wrappedChildren}</>;
🧹 Nitpick comments (1)
src/features/formData/FormDataWrite.tsx (1)

816-848: Simplify the lookup condition logic.

The condition on lines 832-833 is somewhat convoluted. Since foundInDataModel already checks lookupTool && (!lookupErr || lookupErr.error !== 'missingProperty'), the subsequent check for lookupErr?.error !== 'missingRepeatingGroup' can be simplified.

Additionally, line 834 uses reference?.field with optional chaining, but we've already confirmed reference is defined on line 826.

Consider this refactor for clarity:

   return useShallowSelector((v) => {
     if (!reference) {
       return emptyArray;
     }
 
-    // When lookupTool is available and doesn't report a missing repeating group error, we know there's no
-    // repeating group structure in this path, so we can return the field as-is.
-    const foundInDataModel = lookupTool && (!lookupErr || lookupErr.error !== 'missingProperty');
-    if (foundInDataModel && lookupErr?.error !== 'missingRepeatingGroup') {
-      return [reference?.field];
+    // When the field exists in the data model and is not part of a repeating group,
+    // we can return the field as-is without enumeration.
+    if (lookupTool && lookupErr?.error !== 'missingRepeatingGroup' && lookupErr?.error !== 'missingProperty') {
+      return [reference.field];
     }
 
     // If lookupTool is not available (e.g., in tests), or if there's a missingRepeatingGroup error,
     // we need to check the actual data to find all matching paths.
     const formData = v.dataModels[reference.dataType]?.debouncedCurrentData;
     if (!formData) {
       return [];
     }
 
     const paths: string[] = [];
     collectMatchingFieldPaths(formData, reference.field.split('.'), '', 0, paths);
     return paths.length === 0 ? [reference.field] : paths.sort();
   });
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a64e36 and 26e42f7.

📒 Files selected for processing (3)
  • src/features/formData/FormDataWrite.tsx (2 hunks)
  • src/features/validation/expressionValidation/ExpressionValidation.tsx (2 hunks)
  • src/utils/layout/DataModelLocation.tsx (2 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Avoid using any and unnecessary type casts (as Type) in TypeScript; prefer precise typings and refactor existing casts/anys
For TanStack Query, use objects to manage query keys and functions, and centralize shared options via queryOptions

Files:

  • src/features/formData/FormDataWrite.tsx
  • src/features/validation/expressionValidation/ExpressionValidation.tsx
  • src/utils/layout/DataModelLocation.tsx
🧬 Code graph analysis (2)
src/features/formData/FormDataWrite.tsx (1)
src/features/datamodel/DataModelsProvider.tsx (1)
  • DataModels (380-421)
src/features/validation/expressionValidation/ExpressionValidation.tsx (5)
src/features/validation/index.ts (2)
  • FieldValidations (92-94)
  • IExpressionValidation (222-227)
src/features/datamodel/DataModelsProvider.tsx (1)
  • DataModels (380-421)
src/features/validation/validationContext.tsx (1)
  • Validation (320-375)
src/utils/layout/DataModelLocation.tsx (1)
  • NestedDataModelLocationProviders (170-189)
src/utils/layout/useExpressionDataSources.ts (2)
  • useExpressionDataSources (104-167)
  • ExpressionDataSources (37-55)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Install
  • GitHub Check: Type-checks, eslint, unit tests and SonarCloud
🔇 Additional comments (8)
src/features/validation/expressionValidation/ExpressionValidation.tsx (5)

43-51: LGTM - Collector memoization prevents infinite loop.

The collector object is now properly memoized with useMemo and an empty dependency array, which prevents the infinite render loop flagged in the previous review. While the implementation differs slightly from the suggested fix (inline function vs. separate useCallback), it achieves the same stability guarantee since React's setState functions are stable and useMemo with empty deps only runs once.


53-59: LGTM - Effect properly updates validation state.

The effect correctly updates data model validations when allFieldValidations changes, with appropriate guards for missing dataElementId.


61-78: LGTM - Rendering logic is clean and correct.

The early returns prevent rendering when necessary data is unavailable, and each field validation is properly keyed and receives the correct props.


80-110: LGTM - Per-field path enumeration properly handled.

This component correctly uses FD.useDebouncedAllPaths to enumerate all concrete field paths (handling repeating groups), then wraps each path in NestedDataModelLocationProviders to establish the proper context for validation. This aligns with the PR objective of fixing component lookups inside repeating groups.


112-164: LGTM - Validation computation is correct and efficient.

The component properly:

  • Sets up expression data sources with the correct context
  • Evaluates validation conditions and messages using evalExpr
  • Collects validation results and reports them to the collector
  • Uses appropriate memoization (dataSources via useMemo) to prevent unnecessary effect re-runs
src/utils/layout/DataModelLocation.tsx (3)

25-28: LGTM - Interface is clear and well-typed.

The LocationProps interface properly defines the props for DataModelLocationProvider.


30-30: LGTM - Cleaner type definition.

Using PropsWithChildren<LocationProps> is cleaner and more maintainable than an inline anonymous type.


122-161: LGTM - Array index extraction is correct.

The parseGroupContexts function properly parses field paths to extract array indexes and build group contexts. The logic correctly handles:

  • Multiple array indexes in a single path
  • Building incremental paths for nested structures
  • Edge cases like empty fields or no array indexes

Copy link

sonarqubecloud bot commented Oct 3, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
D Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@olemartinorg olemartinorg merged commit 2e9f231 into main Oct 3, 2025
13 of 16 checks passed
@olemartinorg olemartinorg deleted the bug/component-lookups-expression-validation branch October 3, 2025 08:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backport-ignore This PR is a new feature and should not be cherry-picked onto release branches kind/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants